package com.ygqh.baby.excel;

import com.alibaba.fastjson.JSON;
import com.foxinmy.weixin4j.util.StringUtil;
import com.ygqh.baby.excel.annotation.ExcelField;
import com.ygqh.baby.excel.domain.AbstractExcelReadEntity;
import com.ygqh.baby.excel.domain.OrderDetailSendEntity;
import com.ygqh.baby.excel.exception.ExcelException;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 〈〉
 *
 * @author guohao
 * @date 2019/4/19 12:54
 * @since 1.0.0
 */
class ExcelReadHandler {


    static <T extends AbstractExcelReadEntity> List<T> readExcel(Workbook workbook, Class<T> clazz) {

        try {
            List<ExcelRowData> dataList = null;
            Map<Integer, Field> headMap = null;
            for (int numSheet = 0; numSheet < workbook.getNumberOfSheets(); numSheet++) {
                Sheet sheetAt = workbook.getSheetAt(numSheet);
                if (sheetAt == null) {
                    continue;
                }
                // Read the Row
                headMap = headMap == null ? checkExcelAndGetHead(workbook, clazz) : headMap;

                List<ExcelRowData> sheetData = doReadExcelSheet(sheetAt, headMap);

                if (dataList == null) {
                    dataList = new ArrayList<>();
                }
                dataList.addAll(sheetData);

            }
            assert dataList != null;
            return convertToBean(dataList, headMap, clazz);

        } catch (Exception e) {
            throw new RuntimeException("importExcel fail ," + e.getMessage());
        }
    }

    private static <T extends AbstractExcelReadEntity> List<T> convertToBean(List<ExcelRowData> dataList, Map<Integer, Field> headMap, Class<T> clazz) {

        List<T> list = new ArrayList<>();
        dataList.forEach(excelRowData -> {
            try {
                T bean = clazz.newInstance();
                StringBuilder errorMsg = new StringBuilder();
                List<ExcelColumn> data = excelRowData.getData();
                data.forEach(excelColumn -> {
                    Field field = headMap.get(excelColumn.getIndex());
                    String fieldSetMethodName = MethodUtils.getFieldSetMethodName(field.getName());
                    Method fieldSetMet;
                    Class<?> fieldType = field.getType();
                    try {
                        fieldSetMet = clazz.getMethod(fieldSetMethodName, field
                                .getType());
                    } catch (NoSuchMethodException e) {
                        e.printStackTrace();
                        throw new ExcelException(excelRowData.getSheetName() + " 第" + excelRowData.getRowNum() + "行 反射获取" + fieldSetMethodName + "方法失败。" + e.getMessage());
                    }
                    try {
                        if (StringUtil.isBlank(excelColumn.getValue())) {
                            errorMsg.append(excelColumn.getName()).append("不能为空");
                        } else {
                            fieldSetMet.invoke(bean, convertType(fieldType, excelColumn.getValue()));
                        }
                    } catch (IllegalAccessException | InvocationTargetException e) {
                        e.printStackTrace();
                        throw new ExcelException(excelRowData.getSheetName() + " 第" + excelRowData.getRowNum() + "行" + fieldSetMethodName + " 失败。" + e.getMessage());
                    }
                });
                if (errorMsg.length() > 0) {
                    setErrorMsg(clazz, bean, errorMsg.toString());
                }

                list.add(bean);
            } catch (IllegalAccessException | InstantiationException e) {
                e.printStackTrace();
                throw new ExcelException(excelRowData.getSheetName() + " 第" + excelRowData.getRowNum() + "行 " + clazz.getSimpleName() + "反射 失败。" + e.getMessage());
            }
        });
        return list;
    }

    private static <T extends AbstractExcelReadEntity> void setErrorMsg(Class<T> clazz, T bean, String msg) {
        try {
            Method method = clazz.getMethod("setErrorMsg", String.class);
            method.invoke(bean, msg);
        } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
            e.printStackTrace();
            throw new ExcelException("设置错误信息失败。" + msg);
        }
    }

    /**
     * 类型转换
     *
     * @param clazz 转换目标类型
     * @param value 值
     * @return Object
     */
    private static Object convertType(Class clazz, String value) {
        if (Integer.class == clazz || int.class == clazz) {
            return Integer.valueOf(value);
        }
        if (Short.class == clazz || short.class == clazz) {
            return Short.valueOf(value);
        }
        if (Byte.class == clazz || byte.class == clazz) {
            return Byte.valueOf(value);
        }
        if (Character.class == clazz || char.class == clazz) {
            return value.charAt(0);
        }
        if (Long.class == clazz || long.class == clazz) {
            return Long.valueOf(value);
        }
        if (Float.class == clazz || float.class == clazz) {
            return Float.valueOf(value);
        }
        if (Double.class == clazz || double.class == clazz) {
            return Double.valueOf(value);
        }
        if (Boolean.class == clazz || boolean.class == clazz) {
            return Boolean.valueOf(value.toLowerCase());
        }
        if (BigDecimal.class == clazz) {
            return new BigDecimal(value);
        }

        return value;
    }

    private static List<ExcelRowData> doReadExcelSheet(Sheet sheetAt, Map<Integer, Field> headMap) {
        List<ExcelRowData> rowDataList = null;
        for (int rowNum = 1; rowNum <= sheetAt.getLastRowNum(); rowNum++) {
            List<ExcelColumn> dataList = new ArrayList<>();
            Row row = sheetAt.getRow(rowNum);
            headMap.keySet().forEach(index -> {
                Field field = headMap.get(index);
                Cell cell = row.getCell(index);
                ExcelColumn column = new ExcelColumn();
                column.setIndex(index);
                column.setName(field.getAnnotation(ExcelField.class).head());
                column.setValue(getCellValue(cell));
                dataList.add(column);
            });
            ExcelRowData rowData = new ExcelRowData();
            rowData.setRowNum(rowNum);
            rowData.setSheetName(sheetAt.getSheetName());
            rowData.setData(dataList);
            if (rowDataList == null) {
                rowDataList = new ArrayList<>();
            }
            rowDataList.add(rowData);
        }
        return rowDataList;
    }

    /**
     * 验证并获取excel 头信息
     *
     * @param workbook excel 工作簿
     * @param clazz    映射目标类 class
     * @return Map  ---- Integer：列脚标 Field：映射目标类属性
     */
    private static <T extends AbstractExcelReadEntity> Map<Integer, Field> checkExcelAndGetHead(Workbook workbook, Class<T> clazz) {
        Sheet firstSheet = workbook.getSheetAt(0);
        if (firstSheet == null) {
            throw new ExcelException("excel 为空");
        }
        Map<String, Field> requiredField = getRequiredField(clazz);
        Map<Integer, Field> headMap = new HashMap<>(requiredField.size());

        Row firstRow = firstSheet.getRow(0);
        int matchCount = 0;
        for (Cell cell : firstRow) {
            String stringCellValue = cell.getStringCellValue();
            if (requiredField.containsKey(stringCellValue)) {
                matchCount++;
                headMap.put(cell.getColumnIndex(), requiredField.get(stringCellValue));
            }
        }
        if (matchCount != requiredField.size()) {
            throw new ExcelException("excel 缺失必须项");
        }
        return headMap;
    }

    /**
     * 获取必填项
     *
     * @param clazz 目标类class
     * @return Map --- String : Excel 列名，Field : 针对目标类具体成员属性
     */
    private static <T extends AbstractExcelReadEntity> Map<String, Field> getRequiredField(Class<T> clazz) {
        List<Field> allField = getAllField(clazz);
        return allField.stream().filter(field -> {
            ExcelField annotation = field.getAnnotation(ExcelField.class);
            return annotation != null && annotation.required();
        }).collect(Collectors.toMap(field -> field.getAnnotation(ExcelField.class).head(), Function.identity()));
    }

    private static <T extends AbstractExcelReadEntity> List<Field> getAllField(Class<T> clazz) {
        Class<? super T> tempClass = clazz;
        List<Field> fieldArrayList = new ArrayList<>();
        while (!tempClass.equals(Object.class)) {
            fieldArrayList.addAll(Arrays.asList(tempClass.getDeclaredFields()));
            tempClass = tempClass.getSuperclass();
        }
        return fieldArrayList;

    }

    /**
     * 获取Excel单元格中值
     *
     * @param cell Excel单元格
     * @return value
     */
    private static String getCellValue(Cell cell) {
        if (cell == null) {
            return null;
        }
        if (cell.getCellType() == Cell.CELL_TYPE_BOOLEAN) {
            // 返回布尔类型的值
            return String.valueOf(cell.getBooleanCellValue());
        } else if (cell.getCellType() == Cell.CELL_TYPE_NUMERIC) {
            DecimalFormat df = new DecimalFormat("0.00");
            // 返回数值类型的值
            return df.format(cell.getNumericCellValue());
        } else {
            // 返回字符串类型的值
            return String.valueOf(cell.getStringCellValue());
        }
    }

    /**
     * 获取properties的set和get方法
     */
    static class MethodUtils {
        private static final String SET_PREFIX = "set";
        private static final String GET_PREFIX = "get";

        private static String capitalize(String name) {
            if (name == null || name.length() == 0) {
                return name;
            }
            return name.substring(0, 1).toUpperCase() + name.substring(1);
        }

        static String getFieldSetMethodName(String propertyName) {
            return SET_PREFIX + capitalize(propertyName);
        }

        public static String getFieldGetMethodName(String propertyName) {
            return GET_PREFIX + capitalize(propertyName);
        }
    }

    public static void main(String[] args) {
        Field[] declaredFields = OrderDetailSendEntity.class.getDeclaredFields();
        List<Field> collect = Arrays.stream(declaredFields).filter(field -> {
            ExcelField annotation = field.getAnnotation(ExcelField.class);
            return annotation != null && annotation.required();
        }).collect(Collectors.toList());
        System.out.println(JSON.toJSONString(collect));

        System.out.println(AbstractExcelReadEntity.class.getSuperclass().getSimpleName());
    }
}
